//-------------------------------------------------------------------------------------------------
//  math_test.cpp - Testing the math library (matrices, vectors, etc...)
//  (c) 2009 Simon Miessler
//-------------------------------------------------------------------------------------------------

#include "pch.h"
#include "math_test.h"

#include <engine/engine.core/Timer.hpp>

#include "Random.hpp"
#include "math_compare.h"
#include "math_operators.h"
#include "test_quadtree.hpp"




void test_vector2()
{
	RandomCache cache;

	// Test #0: Size & memory layout
	ASSERT_EQUAL( sizeof( D3DXVECTOR2 ), sizeof( float2 ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR2, x ), offsetof( float2, x ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR2, y ), offsetof( float2, y ) );

	// Test #1: Addition
	add_test<D3DXVECTOR2,float2>(cache);

	// Test #2: Subtraction
	sub_test<D3DXVECTOR2,float2>(cache);

	// Test #3: Multiplication
	mul_test<D3DXVECTOR2,float2>(cache);

	// Test #4: Division
	div_test<D3DXVECTOR2,float2>(cache);

	// Test #5: Dot product
	dot_test<D3DXVECTOR2,float2>(cache);

	// Test #6: Length
	length_test<D3DXVECTOR2,float2>(cache);

	// Test #7: Serialization
	serialize_test<float2, float2>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void test_vector3()
{
	RandomCache cache;

	// Test #0: Size & memory layout
	ASSERT_EQUAL( sizeof( D3DXVECTOR3 ), sizeof( float3 ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR3, x ), offsetof( float3, x ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR3, y ), offsetof( float3, y ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR3, z ), offsetof( float3, z ) );

	// Test #1: Addition
	add_test<D3DXVECTOR3,float3>(cache);

	// Test #2: Subtraction
	sub_test<D3DXVECTOR3,float3>(cache);

	// Test #3: Multiplication
	mul_test<D3DXVECTOR3,float3>(cache);

	// Test #4: Division
	div_test<D3DXVECTOR3,float3>(cache);

	// Test #5: Cross product
	cross_test<D3DXVECTOR3,float3>(cache);

	// Test #6: Dot product
	dot_test<D3DXVECTOR3,float3>(cache);

	// Test #7: Length
	length_test<D3DXVECTOR3,float3>(cache);

	// Test #8: Serialization
	serialize_test<float3, float3>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void test_vector4()
{
	RandomCache cache;

	// Test #0: Size & memory layout
	ASSERT_EQUAL( sizeof( D3DXVECTOR4 ), sizeof( float4 ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR4, x ), offsetof( float4, x ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR4, y ), offsetof( float4, y ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR4, z ), offsetof( float4, z ) );
	ASSERT_EQUAL( offsetof( D3DXVECTOR4, w ), offsetof( float4, w ) );

	// Test #1: Addition
	add_test<D3DXVECTOR4,float4>(cache);

	// Test #2: Subtraction
	sub_test<D3DXVECTOR4,float4>(cache);

	// Test #3: Multiplication
	mul_test<D3DXVECTOR4,float4>(cache);

	// Test #4: Division
	div_test<D3DXVECTOR4,float4>(cache);

	// Test #5: Dot product
	dot_test<D3DXVECTOR4,float4>(cache);

	// Test #6: Length
	length_test<D3DXVECTOR4,float4>(cache);

	// Test #7: Serialization
	serialize_test<float4, float4>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void test_rectangle()
{
	typedef std::pair<double, double>   range;
	typedef math::rectangle<double>     rectangle;
	typedef math::vector2<double>       vector2;

	//
	// Test 1: Prerequisites for the rectangle intersection tests are working functions for
	//         range/number and range/range intersection tests
	//

	range r(0.0, 5.0);
	ASSERT(math::range_intersect(0.0, r) == true);
	ASSERT(math::range_intersect(2.0, r) == true);
	ASSERT(math::range_intersect(5.0, r) == true);
	ASSERT(math::range_intersect(-2.0, r) == false);
	ASSERT(math::range_intersect(5.01, r) == false);

	//
	// Test 2: Intersection tests (without calculating the shape)
	//

	rectangle r1 = rectangle::corners(0, 0, 1, 1);
	rectangle r2 = rectangle::corners(0.25, 0.25, 0.5, 0.5);
	rectangle r3 = rectangle::pointSize(1, 1, 0, 0);
	vector2   v1(0, 0);
	vector2   v2(1, 1);
	vector2   v3(-1, -1);
	ASSERT(r1.intersects(r2) == true);
	ASSERT(r1.intersects(r3) == true);
	ASSERT(r2.intersects(r3) == false);
	ASSERT(r1.intersects(v1) == true);
	ASSERT(r1.intersects(v2) == true);
	ASSERT(r1.intersects(v3) == false);
	ASSERT(r2.intersects(v1) == false);
	ASSERT(r2.intersects(v2) == false);
	ASSERT(r2.intersects(v3) == false);

	// Detailed test about the intersection rectangle
	rectangle::opt i1 = r1.intersected(r2);
	rectangle::opt i2 = r1.intersected(r3);
	rectangle::opt i3 = r2.intersected(r3);

	ASSERT(i1);
	ASSERT_EQUAL(rectangle::corners(0.25, 0.25, 0.5, 0.5), *i1);
	ASSERT(i2);
	ASSERT_EQUAL(rectangle::pointSize(1, 1, 0, 0), *i2);
	ASSERT(!i3);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




/**
 * Testing the matrix2x2 implementation is a rather basic test.
 * There is no DirectX counterpart, so a lot of stuff remains untested.
 */
void test_matrix2x2()
{
	RandomCache cache;

	// Test #1: column and row access
	float2x2 m1( 12,      -13,
					   -41.12f, 1392.131f );

	ASSERT_EQUAL( float2( 12, -41.12f ), m1.col1() );
	ASSERT_EQUAL( float2( -13, 1392.131f ), m1.col2() );

	ASSERT_EQUAL( float2( 12, -13 ), m1.row1() );
	ASSERT_EQUAL( float2( -41.12f, 1392.131f ), m1.row2() );


	// Test #2: Identity (compared against hand constructed identity matrix)
	float2x2 m2;
	m2.identity();
	ASSERT_EQUAL( float2x2( 1, 0, 0, 1 ), m2 );

	// Test #3: Serialization
	serialize_test<float2x2, float2x2>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




/**
 * Testing the matrix3x3 implementation is a rather basic test.
 * There is no DirectX counterpart, so a lot of stuff remains truly untested.
 */
void test_matrix3x3()
{
	RandomCache cache;

	// Test #1: column and row access
	float3x3 m1( 12,      -13,    -8871.0134f,
					  -41.12f, 1392.131f, 741,
					  0, -1831, 84.0001f );

	ASSERT_EQUAL( float3( 12, -41.12f, 0 ), m1.col1() );
	ASSERT_EQUAL( float3( -13, 1392.131f, -1831 ), m1.col2() );
	ASSERT_EQUAL( float3( -8871.0134f, 741, 84.0001f ), m1.col3() );

	ASSERT_EQUAL( float3( 12, -13, -8871.0134f ), m1.row1() );
	ASSERT_EQUAL( float3( -41.12f, 1392.131f, 741 ), m1.row2() );
	ASSERT_EQUAL( float3( 0, -1831, 84.0001f ), m1.row3() );


	// Test #2: Identity (compared against hand constructed identity matrix)
	float3x3 m2;
	m2.identity();
	ASSERT_EQUAL( float3x3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), m2 );

	float4x4 m3(m2, float3(0, 0, 0));
	float3 p1 = cache.generate<float3>();
	float3 p2 = cache.generate<float3>();
	p1 = math::transform(m2, p1);
	p2 = math::transform(m3, p2);
	ASSERT_EQUAL(p2, p1);

	// Test #3: Serialization
	serialize_test<float3x3, float3x3>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void test_quaternion()
{
	RandomCache cache;

	// Test #1: Identity
	quaternion     q1;
	D3DXQUATERNION q2;
	D3DXQuaternionIdentity(&q2);
	ASSERT_EQUAL(q2, q1);

	// Test #2: Create a quaternion from a rotation axis and angle
	float a = cache.generate<float>();
	float3 axis1 = cache.generate<float3>();
	D3DXVECTOR3 axis2 = cache.generate<D3DXVECTOR3>();
	q1 = quaternion::fromAxis(axis1, angle::radians(a));
	D3DXQuaternionRotationAxis(&q2, &axis2, a);
	ASSERT_EQUAL(axis2, axis1);
	ASSERT_EQUAL(q2, q1);

	// Test #3: Multiply two quaternions
	q1 = quaternion::fromAxis(axis1, angle::radians(a));
	axis1.x += 5;
	quaternion q3 = quaternion::fromAxis(axis1, angle::radians(a / 10));
	D3DXQUATERNION q4;

	// Synchronize both quaternions
	q1 = cache.generate<quaternion>();
	q2 = cache.generate<D3DXQUATERNION>();
	q3 = cache.generate<quaternion>();
	q4 = cache.generate<D3DXQUATERNION>();
	ASSERT_EQUAL(q2, q1);
	ASSERT_EQUAL(q4, q3);

	q3 = q1 * q3;
	D3DXQuaternionMultiply(&q4, &q4, &q2);
	ASSERT_EQUAL(q4, q3);

	// Test #4: Inversing a quaternion
	q3.inverse();
	D3DXQuaternionNormalize(&q4, &q4);
	D3DXQuaternionInverse(&q4, &q4);
	ASSERT_EQUAL(q4, q3);

	// Test #5: Calculating the logarithm of a quaternion

	// They must be synchronized: probably because numerically instability of floats
	q3.norm();
	q3.w = q4.w; q3.x = q4.x; q3.y = q4.y; q3.z = q4.z;

	q3.ln();
	D3DXQuaternionLn(&q4, &q4);
	ASSERT_EQUAL(q4, q3);

	// Test #6: Calculating the exp of a quaternion
	q3.exp();
	D3DXQuaternionExp(&q4, &q4);
	ASSERT_EQUAL(q4, q3);

	// Test #7: Calculating inverse
	q3.inverse();
	D3DXQuaternionInverse(&q4, &q4);
	ASSERT_EQUAL(q4, q3);

	// Test #8: Normalizing a quaternion
	q3.norm();
	D3DXQuaternionNormalize(&q4, &q4);
	ASSERT_EQUAL(q4, q3);
	q3 = cache.generate<quaternion>();
	q4 = cache.generate<D3DXQUATERNION>();
	q3.conjugate();
	D3DXQuaternionConjugate(&q4, &q4);
	ASSERT_EQUAL(q4, q3);

	// Test #9: An Identity matrix must be identical to a matrix, created from an identity quaternion
	ASSERT(float3x3() == float3x3(quaternion()));

	// Test #10: Spherical Linear interpolation
	quaternion q5 = cache.generate<quaternion>(); q5.norm();
	D3DXQUATERNION q6 = cache.generate<D3DXQUATERNION>(); D3DXQuaternionNormalize(&q6, &q6);
	ASSERT_EQUAL(q4, q3); D3DXQuaternionNormalize(&q4, &q4); q3.norm();
	ASSERT_EQUAL(q6, q5);
	q1 = slerp(q3, q5, 0.5f);
	D3DXQuaternionSlerp(&q2, &q4, &q6, 0.5f);
	ASSERT_EQUAL(q2, q1);

	// Test #11: Matrix construction (partially)
	q1 = quaternion();
	float3x3 m1 = q1;
	ASSERT(m1.isIdentity());

	// Creating a matrix from a quaternion and creating one directly (where both should represent
	// the same direction), should result in 2 identical matrices
	q1 = quaternion::fromAxis(float3(1, 0, 0), angle::degrees(90));
	m1 = float3x3::rotationX(angle::degrees(90));
	float3x3 m2 = q1;
	ASSERT_EQUAL(m1, m2);

	q1 = quaternion::fromAxis(float3(0, 1, 0), angle::degrees(90));
	m1 = float3x3::rotationY(angle::degrees(90));
	m2 = q1;
	ASSERT_EQUAL(m1, m2);

	q1 = quaternion::fromAxis(float3(0, 0, 1), angle::degrees(90));
	m1 = float3x3::rotationZ(angle::degrees(90));
	m2 = q1;
	ASSERT_EQUAL(m1, m2);

	// Test #11: Vector transformation/rotation
	q1 = cache.generate<quaternion>().norm();
	m1 = q1;
	float3 v1 = float3(100, -5000, 1000);
	float3 v2 = v1;
	v1 = math::transform(q1, v1);
	v2 = math::transform(m1, v2);
	ASSERT_EQUAL(v2, v1);

	// Test #12: Serialization
	serialize_test<quaternion, quaternion>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




/**
 * Testing the 4x4 matrix is an integral part of this unit test because it is the only
 * matrix implementation that has a directx counterpart. The matrix2x2 and matrix3x3 cannot
 * be specifically tested because of that.
 */
void test_matrix4x4()
{
	RandomCache cache;

	// Test #0: Size & memory layout
	ASSERT_EQUAL( sizeof( D3DXMATRIX ), sizeof( float4x4 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _11 ), offsetof( float4x4, _11 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _12 ), offsetof( float4x4, _12 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _13 ), offsetof( float4x4, _13 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _14 ), offsetof( float4x4, _14 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _21 ), offsetof( float4x4, _21 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _22 ), offsetof( float4x4, _22 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _23 ), offsetof( float4x4, _23 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _24 ), offsetof( float4x4, _24 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _31 ), offsetof( float4x4, _31 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _32 ), offsetof( float4x4, _32 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _33 ), offsetof( float4x4, _33 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _34 ), offsetof( float4x4, _34 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _41 ), offsetof( float4x4, _41 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _42 ), offsetof( float4x4, _42 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _43 ), offsetof( float4x4, _43 ) );
	ASSERT_EQUAL( offsetof( D3DXMATRIX, _44 ), offsetof( float4x4, _44 ) );

	// Test #1: column and row access
	float4x4 m1( 12, -13, -41.12f, 1392.131f,
					   7741, 91, -1, 181.13f,
					   1182, 0, -12, 192.01f,
					   12, -12.12f, 75, 1 );

	ASSERT_EQUAL( float4( 12, 7741, 1182, 12 ), m1.col1() );
	ASSERT_EQUAL( float4( -13, 91, 0, -12.12f ), m1.col2() );
	ASSERT_EQUAL( float4( -41.12f, -1, -12, 75 ), m1.col3() );
	ASSERT_EQUAL( float4( 1392.131f, 181.13f, 192.01f, 1 ), m1.col4() );

	ASSERT_EQUAL( float4( 12, -13, -41.12f, 1392.131f ), m1.row1() );
	ASSERT_EQUAL( float4( 7741, 91, -1, 181.13f ), m1.row2() );
	ASSERT_EQUAL( float4( 1182, 0, -12, 192.01f ), m1.row3() );
	ASSERT_EQUAL( float4( 12, -12.12f, 75, 1 ), m1.row4() );

	// Test #2: addition
	add_test<D3DXMATRIX,float4x4>(cache);

	// Test #3: subtraction
	sub_test<D3DXMATRIX,float4x4>(cache);

	// Test #4: multiplication
	mul_test<D3DXMATRIX,float4x4>(cache);

	// Test #5: identity
	identity_test<D3DXMATRIX,float4x4>(cache);

	// Test #6: determinant
	determinant_test<D3DXMATRIX,float4x4>(cache);

	// Test #7: inverse
	inverse_test<D3DXMATRIX,float4x4>(cache);

	// Test #8: Scale
	scale_test<D3DXMATRIX,float4x4>(cache);

	transform_test<D3DXMATRIX,D3DXVECTOR4,float4x4,float4>(cache);

	// Test #9: Perspective matrix (LookAt)
	/*perspective_test<D3DXMATRIX,float4x4>(cache);

	// Test #10: Projection matrix
	projection_test<D3DXMATRIX,float4x4>(cache);*/

	// Test #11: Serialization
	serialize_test<float4x4, float4x4>(cache);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void test_octree()
{
	// Create an octree that grows 10 units in each axes' direction
	/*dynamic_octree<uint32> tree( float3( 0, 0, 0 ), float3( 10, 10, 10 ) );*/
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void runMath()
{
	cute::suite s;

	s.push_back( cute::test( test_vector2,    "math::vector2<float>" ) );
	s.push_back( cute::test( test_vector3,    "math::vector3<float>" ) );
	s.push_back( cute::test( test_vector4,    "math::vector4<float>" ) );

	s.push_back( cute::test( test_rectangle,  "math::rectangle<float>" ) );
	s.push_back( cute::test( test_quadtree(), "math::quadtree<float>" ) );

	s.push_back( cute::test( test_matrix2x2,  "math::matrix2x2<float>" ) );
	s.push_back( cute::test( test_matrix3x3,  "math::matrix3x3<float>" ) );
	s.push_back( cute::test( test_matrix4x4,  "math::matrix4x4<float>" ) );

	s.push_back( cute::test( test_quaternion, "math::quaternion<float>" ) );

	/*s.push_back( cute::test( test_octree, "math::octree" ) );*/

	cute::ide_listener lis;
	cute::makeRunner( lis )( s, "Math Test" );
}
///////////////////////////////////////////////////////////////////////////////////////////////////
